package com.spzx.order.service.impl;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.spzx.cart.api.RemoteCartService;
import com.spzx.cart.api.domain.CartInfo;
import com.spzx.common.core.constant.SecurityConstants;
import com.spzx.common.core.context.SecurityContextHolder;
import com.spzx.common.core.domain.R;
import com.spzx.common.core.exception.ServiceException;
import com.spzx.common.core.utils.StringUtils;
import com.spzx.common.core.utils.uuid.UUID;
import com.spzx.common.rabbit.constant.MqConst;
import com.spzx.common.rabbit.service.RabbitService;
import com.spzx.common.security.utils.SecurityUtils;
import com.spzx.order.api.domain.OrderItem;
import com.spzx.order.domain.OrderLog;
import com.spzx.order.domain.form.OrderForm;
import com.spzx.order.domain.vo.TradeVo;
import com.spzx.order.mapper.OrderItemMapper;
import com.spzx.order.mapper.OrderLogMapper;
import com.spzx.product.api.RemoteProductService;
import com.spzx.product.api.domain.ProductSku;
import com.spzx.product.api.domain.SkuLockVo;
import com.spzx.product.api.domain.vo.SkuPriceVo;
import com.spzx.user.api.RemoteUserAddressService;
import com.spzx.user.api.domain.UserAddress;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import com.spzx.order.mapper.OrderInfoMapper;
import com.spzx.order.api.domain.OrderInfo;
import com.spzx.order.service.IOrderInfoService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

/**
 * 订单Service业务层处理
 *
 * @author atguigu
 * @date 2025-03-13
 */
@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements IOrderInfoService {
    @Autowired
    private OrderInfoMapper orderInfoMapper;

    @Autowired
    private OrderItemMapper orderItemMapper;

    @Autowired
    private RemoteCartService remoteCartService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private RemoteUserAddressService remoteUserAddressService;

    @Autowired
    private OrderLogMapper orderLogMapper;

    @Autowired
    private RemoteProductService remoteProductService;

    @Autowired
    private RabbitService rabbitService;

    /**
     * 查询订单列表
     *
     * @param orderInfo 订单
     * @return 订单
     */
    @Override
    public List<OrderInfo> selectOrderInfoList(OrderInfo orderInfo) {
        return orderInfoMapper.selectOrderInfoList(orderInfo);
    }

    @Override
    public OrderInfo selectOrderInfoById(Long id) {
        OrderInfo orderInfo = baseMapper.selectById(id);
        LambdaQueryWrapper<OrderItem> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(OrderItem::getOrderId, id);

        List<OrderItem> orderItemList = orderItemMapper.selectList(queryWrapper);
        orderInfo.setOrderItemList(orderItemList);

        return orderInfo;
    }

    @Override
    public TradeVo orderTradeData() {
        //先拿出结算商品列表，然后才能计算结算总金额和生成交易号

        Long userId = SecurityContextHolder.getUserId();
        //远程调用获取购物车中选中的商品
        R<List<CartInfo>> cartInfoCheckedResult = remoteCartService.getCartCheckedList(userId, SecurityConstants.INNER);
        if (R.FAIL == cartInfoCheckedResult.getCode()) {
            throw new ServiceException(cartInfoCheckedResult.getMsg());
        }

        //获取购物车中选中的商品
        List<CartInfo> cartInfoList = cartInfoCheckedResult.getData();
        if (!CollectionUtils.isEmpty(cartInfoList)) {
            //组装orderItemList
            List<OrderItem> orderItemList = cartInfoList.stream().map(cartInfo -> {
                OrderItem orderItem = new OrderItem();
                BeanUtils.copyProperties(cartInfo, orderItem);
                return orderItem;
            }).collect(Collectors.toList());
            //商品列表获取结束

            //接下来计算商品总金额
            //方法一：使用Stream流来完成我们的业务
            BigDecimal totalAmount = orderItemList.stream().map(orderItem -> {
                //商品的单价乘以商品的数量，然后再相加得到总金额
                return orderItem.getSkuPrice()
                        .multiply(new BigDecimal(orderItem.getSkuNum()));
                //reduce方法的作用是：累加，将流中的元素进行累计，最终得到一个值
                //参数一：初始值，参数二：每次迭代的计算规则
            }).reduce(BigDecimal.ZERO, BigDecimal::add);

            //方法二：使用for循环
//        BigDecimal totalAmount2 = new BigDecimal(0);
//        for (OrderItem orderItem : orderItemList) {
//            BigDecimal money = orderItem.getSkuPrice()
//                    .multiply(new BigDecimal(orderItem.getSkuNum()));
//            totalAmount2 = money.add(totalAmount2);
//        }
            //到此总金额计算完毕

            //接下来要生成订单号
            String tradeNo = generateTradeNo(userId);

            TradeVo tradeVo = new TradeVo();
            //将商品列表组装到tradeVo中
            tradeVo.setOrderItemList(orderItemList);
            //将总金额组装到tradeVo中
            tradeVo.setTotalAmount(totalAmount);
            //将订单号组装到tradeVo中
            tradeVo.setTradeNo(tradeNo);

            return tradeVo;
        }

        //没有选择商品，所以不需要下单
        return null;

    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Long submitOrder(OrderForm orderForm) {
        //获取用户的userId
        Long userId = SecurityContextHolder.getUserId();

        //1.防止订单重复提交
        //判断订单号是否存在
        //如果订单号存在，那么说明用户是第一次提交订单
        //如果用户在五分钟内已经提交了第二次订单，那么订单号不存在，说明用户已经提交了该订单，不能重复提交
        //如果用户在五分钟以外提交订单，那么订单号也不存在，说明超时了
//        if (!checkTradeNo(userId.toString(), orderForm.getTradeNo())) {
//            throw new ServiceException("抱歉，请勿重复提交订单");
//        }
//
//        //如果订单号存在，那么就删除redis中的订单
//        deleteTradeNo(userId.toString());

        /*
        上面的判断订单号是否存在和删除订单号方法之间，有可能会出现异常，
        导致代码不具备原子性，所以为了解决这个问题，
        使用lua脚本来解决代码没有原子性的问题
         */
        String userTradeKey = "user:tradeNo:" + userId;
        //在redis中获取一个key值，看这个key值是否等于传进来的tradeNo值
        // 将键的值更为 processed  表示已处理  return 1 表示处理成功
        // return -1  键存在但值不匹配，表示重复提交
        // return 0   键不存在，订单过期
        String scriptText = """
                   if redis.call('get', KEYS[1]) == ARGV[1]
                   then
                       redis.call('set', KEYS[1], 'processed')
                       return 1
                   else
                       if redis.call('exists', KEYS[1]) == 1
                       then
                           redis.call('del',KEYS[1])
                           return -1
                       else
                           return 0
                       end
                   end
                """;
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(scriptText);
        redisScript.setResultType(Long.class);
        Long flag = (Long) redisTemplate.execute(
                redisScript,
                Arrays.asList(userTradeKey),
                orderForm.getTradeNo());
        if (flag == 0) {
            throw new ServiceException("操作超时，请退回重试");
        }

        if (flag == -1) {
            throw new ServiceException("请勿重复提交订单");
        }

        //模拟重复提交订单
//        try {
//            Thread.sleep(3000);
//        } catch (InterruptedException e) {
//            throw new RuntimeException(e);
//        }

        //2.获取最新价格并判断价格是否改变
        //批量获取最新价格
        List<OrderItem> orderItemList = orderForm.getOrderItemList();
        //健壮性校验，判断是否为空
        if (CollectionUtils.isEmpty(orderItemList)) {
            throw new ServiceException("请求不合法");
        }

        List<Long> skuIdList = orderItemList.stream().map(orderItem -> {
            return orderItem.getSkuId();
        }).collect(Collectors.toList());
        R<List<SkuPriceVo>> skuPriceListResult = remoteProductService.getSkuPriceList(skuIdList, SecurityConstants.INNER);
        if (R.FAIL == skuPriceListResult.getCode()) {
            throw new ServiceException(skuPriceListResult.getMsg());
        }
        List<SkuPriceVo> skuPriceList = skuPriceListResult.getData();

        //判断价格是否变化
        //skuId作为key，salePrice作为value
        Map<Long, BigDecimal> skuIdSalePriceMap = skuPriceList.stream().collect(Collectors.toMap(SkuPriceVo::getSkuId, SkuPriceVo::getSalePrice));
        String priceCheckResult = "";
        for (OrderItem orderItem : orderItemList) {
            //如果商品价格发生改变，提醒用户重新下单
            //不能这么比，不然的话一个没有.00,一个有.00，最后结果不相同
            //!orderItem.getSkuPrice().equals(skuIdSalePriceMap.get(orderItem.getSkuId()))
            //这样比较就万无一失了
            if (orderItem.getSkuPrice().compareTo(skuIdSalePriceMap.get(orderItem.getSkuId())) != 0) {
                //价格发生了变化
                priceCheckResult += orderItem.getSkuName() + "\n";
            }
        }
        //如果商品的价格发生了变化
        if (!StringUtils.isEmpty(priceCheckResult)) {
            //如果不是立即购买，才执行更新购物车价格
            if (!orderForm.isBuy()) {
                //更新购物车价格
                remoteCartService.updateCartPrice(userId, SecurityConstants.INNER);
            }
            throw new ServiceException(priceCheckResult + "以上商品价格发生了变化，请重新下单");
        }


        //3.TODO
        //校验库存并且锁定库存
        //将orderItemList转换为List<skuLockVo>
        List<SkuLockVo> skuLockVoList = orderItemList.stream().map(orderItem -> {
            SkuLockVo skuLockVo = new SkuLockVo();
            skuLockVo.setSkuId(orderItem.getSkuId());
            skuLockVo.setSkuNum(orderItem.getSkuNum());
            return skuLockVo;
        }).collect(Collectors.toList());

        String checkAndLockResult = remoteProductService.checkAndLock(
                orderForm.getTradeNo(),
                skuLockVoList,
                SecurityConstants.INNER
        ).getData();
        if (StringUtils.isNotEmpty(checkAndLockResult)) {
            throw new ServiceException(checkAndLockResult);
        }


        //4.下单操作
        Long orderId = saveOrder(orderForm);


        //5.下单完成后，删除购物车中所选的商品
        //使用远程调用即可
        //如果不是立即购买，我们才删除购物车中的商品
        if (!orderForm.isBuy()) {
            remoteCartService.deleteCartCheckedList(userId, SecurityConstants.INNER);
        }

        //返回订单id
        return orderId;

    }

    /**
     * 获取用户订单列表
     *
     * @param orderStatus 订单状态
     * @return
     */
    @Override
    public List<OrderInfo> selectUserOrderInfoList(Integer orderStatus) {
        //获取当前用户id
        Long userId = SecurityContextHolder.getUserId();
        //封装查询条件
        LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(OrderInfo::getUserId, userId)
                .eq(orderStatus != null, OrderInfo::getOrderStatus, orderStatus)
                //将订单信息根据创建时间倒序排序
                .orderByDesc(OrderInfo::getCreateTime);
        List<OrderInfo> orderInfoList = baseMapper.selectList(queryWrapper);

        if (!CollectionUtils.isEmpty(orderInfoList)) {
            //获取所有的orderId列表
            List<Long> orderIdList = orderInfoList.stream().map(orderInfo -> {
                return orderInfo.getId();
            }).collect(Collectors.toList());

            //获取所有orderItem
            //方法1：时间复杂度为O(1)，是一个优秀的算法
            LambdaQueryWrapper<OrderItem> queryWrapper1 = new LambdaQueryWrapper<>();
            queryWrapper1.in(OrderItem::getOrderId, orderIdList);
            List<OrderItem> orderItemList = orderItemMapper.selectList(queryWrapper1);

            //方法2：使用for循环遍历
            //这种方式会产生n个sql语句，时间复杂度为O(n)，所以我们不采用这种方式
//            for (OrderInfo orderInfo : orderInfoList) {
//                LambdaQueryWrapper<OrderItem> queryWrapper2 = new LambdaQueryWrapper<>();
//                queryWrapper1.in(OrderItem::getOrderId, orderIdList);
//                List<OrderItem> orderItemList2 = orderItemMapper.selectList(queryWrapper2);
//            }

            //将orderItemList放入Hash中，orderId作为key，orderItemList作为value
            //因为一个orderId下面有多个orderItemList，所以需要先分组
            Map<Long, List<OrderItem>> orderItemMap = orderItemList.stream()
                    .collect(Collectors.groupingBy(OrderItem::getOrderId));
            orderInfoList.forEach(orderInfo -> {
                orderInfo.setOrderItemList(orderItemMap.get(orderInfo.getId()));
            });

        }

        return orderInfoList;

    }

    @Override
    public TradeVo buy(Long skuId) {

        //获取商品sku信息
        R<ProductSku> productSkuResult = remoteProductService.getProductSku(
                skuId,
                SecurityConstants.INNER);
        if (productSkuResult.getCode() == R.FAIL) {
            throw new ServiceException(productSkuResult.getMsg());
        }
        //获取实时价格
        R<SkuPriceVo> skuPriceResult = remoteProductService.getSkuPrice(
                skuId,
                SecurityConstants.INNER);
        if (R.FAIL == skuPriceResult.getCode()) {
            throw new ServiceException(skuPriceResult.getMsg());
        }

        ProductSku productSku = productSkuResult.getData();
        SkuPriceVo skuPrice = skuPriceResult.getData();

        List<OrderItem> orderItemList = new ArrayList<>();
        OrderItem orderItem = new OrderItem();
        orderItem.setSkuId(skuId);
        orderItem.setSkuName(productSku.getSkuName());
        orderItem.setSkuNum(1);
        //填充最新价格
        orderItem.setSkuPrice(skuPrice.getSalePrice());
        orderItem.setThumbImg(productSku.getThumbImg());
        orderItemList.add(orderItem);

        //订单总金额，填充最新价格
        BigDecimal totalAmount = skuPrice.getSalePrice();

        //渲染订单确认页面-生成用户流水号
        String tradeNo = this.generateTradeNo(SecurityUtils.getUserId());

        TradeVo tradeVo = new TradeVo();
        tradeVo.setTotalAmount(totalAmount);
        tradeVo.setOrderItemList(orderItemList);
        tradeVo.setTradeNo(tradeNo);
        //如果是立即购买，那么就设置为true
        tradeVo.setIsBuy(true);
        return tradeVo;
    }

    /**
     * 取消订单
     *
     * @param orderId 订单id
     */
    @Override
    public void cancelOrder(Long orderId) {
        OrderInfo orderInfo = orderInfoMapper.selectById(orderId);
        //status值为零，说明订单是代付款状态
        if (orderInfo != null && orderInfo.getOrderStatus().intValue() == 0) {
            //如果订单是代付款状态，那么订单可以随时取消
            //-1代表已取消
            orderInfo.setOrderStatus(-1);
            orderInfo.setCancelTime(new Date());
            orderInfo.setCancelReason("用户取消订单");
            //将数据更新到数据库中
            baseMapper.updateById(orderInfo);

            //记录订单日志
            OrderLog orderLog = new OrderLog();
            orderLog.setOrderId(orderId);
            //订单日志记录用户已经取消订单
            orderLog.setProcessStatus(-1);
            //将订单日志记录到数据库中
            orderLog.setNote("用户已经取消订单");
            orderLog.setCreateBy(SecurityContextHolder.getUserName());
            orderLog.setUpdateBy(SecurityContextHolder.getUserName());
            orderLog.setOperateUser(SecurityContextHolder.getUserName());
            orderLogMapper.insert(orderLog);

            //发送消息到mq通知商品系统解锁系统库存
            rabbitService.sendMessage(
                    MqConst.EXCHANGE_PRODUCT,
                    MqConst.ROUTING_UNLOCK,
                    //订单号
                    orderInfo.getOrderNo()
            );
        }

    }

    /**
     * 根据订单号获取订单
     *
     * @param orderNo 订单号
     * @return
     */
    @Override
    public OrderInfo getByOrderNo(String orderNo) {
        //根据订单号获取订单数据
        OrderInfo orderInfo = baseMapper.selectOne(
                new LambdaQueryWrapper<OrderInfo>()
                        .eq(OrderInfo::getOrderNo, orderNo));

        //根据订单号中的id获取订单项数据
        List<OrderItem> orderItemList = orderItemMapper.selectList(
                new LambdaQueryWrapper<OrderItem>()
                        .eq(OrderItem::getOrderId, orderInfo.getId())
        );

        //组装订单
        orderInfo.setOrderItemList(orderItemList);

        return orderInfo;
    }

    @Override
    public void processPaySuccess(String orderNo) {
        //根据订单号查询订单信息
        LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(OrderInfo::getOrderNo, orderNo)
                .select(OrderInfo::getId, OrderInfo::getOrderStatus);
        OrderInfo orderInfo = baseMapper.selectOne(queryWrapper);

        //幂等性处理
        //0代表未付款
        if (orderInfo.getOrderStatus().intValue() == 0) {
            //修改为已支付
            orderInfo.setOrderStatus(1);
            orderInfo.setPaymentTime(new Date());
            baseMapper.updateById(orderInfo);
        }

    }

    private Long saveOrder(OrderForm orderForm) {

        Long userId = SecurityContextHolder.getUserId();
        String userName = SecurityContextHolder.getUserName();

        //1.保存订单记录：order_info
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setUserId(userId);
        orderInfo.setNickName(userName);
        orderInfo.setFeightFee(orderForm.getFeightFee());
        orderInfo.setRemark(orderForm.getRemark());
        orderInfo.setOrderNo(orderForm.getTradeNo());

        //远程调用获取用户地址信息
        R<UserAddress> userAddressResult = remoteUserAddressService.getUserAddress(orderForm.getUserAddressId(), SecurityConstants.INNER);
        if (R.FAIL == userAddressResult.getCode()) {
            throw new ServiceException(userAddressResult.getMsg());
        }
        //获取用户地址信息
        UserAddress userAddress = userAddressResult.getData();
        //往orderInfo中设置字段，保存用户地址信息
        orderInfo.setReceiverName(userAddress.getName());
        orderInfo.setReceiverPhone(userAddress.getPhone());
        orderInfo.setReceiverTagName(userAddress.getTagName());
        orderInfo.setReceiverProvince(userAddress.getProvinceCode());
        orderInfo.setReceiverCity(userAddress.getCityCode());
        orderInfo.setReceiverDistrict(userAddress.getDistrictCode());
        orderInfo.setReceiverAddress(userAddress.getFullAddress());

        //计算orderItem中商品的总价格
        List<OrderItem> orderItemList = orderForm.getOrderItemList();
        BigDecimal totalAmount = orderItemList.stream().map(orderItem -> {
            return orderItem.getSkuPrice()
                    .multiply(BigDecimal.valueOf(orderItem.getSkuNum()));
        }).reduce(BigDecimal.ZERO, BigDecimal::add);
        //将商品总价格组装到orderInfo中
        orderInfo.setTotalAmount(totalAmount);
        //优惠券价格
        orderInfo.setCouponAmount(BigDecimal.valueOf(0));
        //原价
        orderInfo.setOriginalTotalAmount(totalAmount);

        //状态为0，代表待付款
        orderInfo.setOrderStatus(0);

        orderInfo.setCreateBy(userName);
        orderInfo.setUpdateBy(userName);
        //最后将数据插入到数据库中
        orderInfoMapper.insert(orderInfo);

        //2.保存订单项记录：order_item
        for (OrderItem orderItem : orderItemList) {
            orderItem.setOrderId(orderInfo.getId());
            orderItem.setCreateBy(userName);
            orderItem.setUpdateBy(userName);
            orderItemMapper.insert(orderItem);
        }

        //3.保存订单日志记录：order_log
        OrderLog orderLog = new OrderLog();
        orderLog.setOrderId(orderInfo.getId());
        orderLog.setProcessStatus(0);
        orderLog.setOperateUser(userName);
        orderLog.setNote("提交订单");
        orderLog.setCreateBy(userName);
        orderLog.setUpdateBy(userName);
        orderLogMapper.insert(orderLog);

        //返回订单号
        return orderInfo.getId();
    }

    /**
     * 渲染订单确认页面-生成用户流水号
     *
     * @param userId
     * @return
     */
    private String generateTradeNo(Long userId) {
        //1.构建流水号Key
        String userTradeKey = "user:tradeNo:" + userId;
        //2.构建流水号value
        String tradeNo = UUID.randomUUID().toString().replaceAll("-", "");
        //3.将流水号存入Redis 暂存5分钟
        redisTemplate.opsForValue().set(userTradeKey, tradeNo, 5, TimeUnit.MINUTES);
        return tradeNo;
    }

    /**
     * 验证页面提交流水号是否有效
     *
     * @param userId
     * @param tradeNo
     * @return
     */
    private Boolean checkTradeNo(String userId, String tradeNo) {
        String userTradeKey = "user:tradeNo:" + userId;
        String redisTradeNo = (String) redisTemplate.opsForValue().get(userTradeKey);
        //查到了订单号返回true，没有查到订单号返回false
        return tradeNo.equals(redisTradeNo);
    }


    /**
     * 删除流水号
     *
     * @param userId
     */
    private void deleteTradeNo(String userId) {
        String userTradeKey = "user:tradeNo:" + userId;
        redisTemplate.delete(userTradeKey);
    }

}
